﻿using System;

namespace MLSharp
{
	/// <summary>
	/// A class for maintaining statistical information about numeric values, converted
	/// from Weka.
	/// </summary>
	public class Stats
	{
		#region Public Properties

		/// <summary>
		/// The number of values seen.
		/// </summary>
		public double Count { get; private set;}

		/// <summary>
		/// The sum of values seen.
		/// </summary>
		public double Sum { get; private set; }

		/// <summary>
		/// The sum of values squared seen.
		/// </summary>
		public double SumSquared { get; private set; }

		/// <summary>
		/// The std deviation of values at the last CalculateDerived() call.
		/// </summary>
		public double StdDev { get; private set; }

		/// <summary>
		/// The mean of values at the last CalculateDerived() call
		/// </summary>
		public double Mean { get; private set; }

		/// <summary>
		/// The minimum value seen, or Double.NaN if no values seen
		/// </summary>
		public double Min { get; private set; }

		/// <summary>
		/// The maximum value seen, or Double.NaN if no values seen
		/// </summary>
		public double Max { get; private set; }

		#endregion

		#region Public Constructors

		/// <summary>
		/// Creates a new empty instance of the Stats class.
		/// </summary>
		public Stats()
		{
			StdDev = double.NaN;
			Mean = double.NaN;
			Min = double.NaN;
			Max = double.NaN;
		}

		#endregion

		#region Public Methods

		///<summary>
		/// Adds a value to the observed values
		///</summary>
		///<param name="value">the observed value</param>
		public void Add(double value)
		{
			Add(value, 1);
		}

		///<summary>
		/// Adds a value that has been seen n times to the observed values
		///</summary>
		///<param name="value">the observed value</param>
		///<param name="n">the number of times to add value</param>
		public void Add(double value, double n)
		{

			Sum += value * n;
			SumSquared += value * value * n;
			Count += n;
			if (double.IsNaN(Min))
			{
				Min = Max = value;
			}
			else if (value < Min)
			{
				Min = value;
			}
			else if (value > Max)
			{
				Max = value;
			}
		}

		///<summary>
		/// Removes a value to the observed values (no checking is done
		/// that the value being removed was actually added).
		///</summary>
		///<param name="value">the observed value</param>
		public void Subtract(double value)
		{
			Subtract(value, 1);
		}

		///<summary>
		/// Subtracts a value that has been seen n times from the observed values
		///</summary>
		///<param name="value">the observed value</param>
		///<param name="n">the number of times to subtract value</param>
		public void Subtract(double value, double n)
		{
			Sum -= value * n;
			SumSquared -= value * value * n;
			Count -= n;
		}

		///<summary>
		/// Tells the object to calculate any statistics that don't have their
		/// values automatically updated during add. Currently updates the mean
		/// and standard deviation.
		///</summary>
		public void CalculateDerived()
		{
			Mean = Double.NaN;
			StdDev = Double.NaN;
			if (Count > 0)
			{
				Mean = Sum / Count;
				StdDev = 0;
				if (Count > 1)
				{
					StdDev = SumSquared - (Sum * Sum) / Count;
					StdDev /= (Count - 1);
					if (StdDev < 0)
					{
						//          System.err.println("Warning: stdDev value = " + stdDev 
						//                             + " -- rounded to zero.");
						StdDev = 0;
					}
					StdDev = Math.Sqrt(StdDev);
				}
			}
		}

		///<summary>
		/// Returns a string summarizing the stats so far
		///</summary>
		///<returns>the summary string</returns>
		public override string ToString()
		{

			CalculateDerived();
			return
			  "Count   " + Count + '\n'
			  + "Min     " + Min + '\n'
			  + "Max     " + Max + '\n'
			  + "Sum     " + Sum + '\n'
			  + "SumSquared   " + SumSquared + '\n'
			  + "Mean    " + Mean + '\n'
			  + "StdDev  " + StdDev + '\n';
		}

		#endregion
	}
}
